/*
 * OpenFMS Input driver for the E-Sky dummie USB transmitter.
 *
 * Copyright (C) 2010 Johan Waldeback
 *  
 * This file is part of the OpenFMS project (http://www.openfms-project.org)
 * 
 * OpenFMS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * \file EskyInput_dll.cpp
 * \brief E-Sky USB transmitter input driver (Windows).
 *
 * The input driver is a dynamic library (a .dll file) with
 * four public functions: \c create, \c destroy, \c get_number_of_channels and \c capture.
 *   - \c create - creates an instance representing a connected device
 *   - \c destroy - frees resources made ny the create function
 *   - \c get_number_of_channels - retruns the number of channels used.
 *   - \c capture - reads ste state of the controller and retruns the captured data.
 */

// Get rid of warning message
// (Make invisible to doxygen)
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#define DIRECTINPUT_VERSION 0x0800
#endif // DOXYGEN_SHOULD_SKIP_THIS


#include <dinput.h>
#include <windows.h>
#include <tchar.h>
#include <stdint.h>
#include <cmath>
#include <cassert>

extern "C" {

/**
 * \class EskyInput_HandleData
 * \brief Internal E-Sky input driver handle structure.
 *
 * Contains all data the E-sky input driver needs to handle a device instance
 */
struct EskyInput_HandleData {
	LPDIRECTINPUT8 di;             /**< Pointer to an DirectInput8 instance */
	LPDIRECTINPUTDEVICE8 joystick; /**< Pointer to a Joystick instance */
	HWND hWnd;                     /**< Handle to dumme window */
};

/**
 * Callback function used to set joysick axes properties 
 * 
 * Set properties of joystick axis:
 *   - Range is set to -10000 to +10000
 */
static BOOL CALLBACK enumAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, VOID* context)
{
	// Get pointer to joystick
	LPDIRECTINPUTDEVICE8 joystick = reinterpret_cast<LPDIRECTINPUTDEVICE8>(context);

	// Seup range structure
    DIPROPRANGE propRange; 
    propRange.diph.dwSize       = sizeof(DIPROPRANGE); 
    propRange.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
    propRange.diph.dwHow        = DIPH_BYID; 
    propRange.diph.dwObj        = instance->dwType;
    propRange.lMin              = -10000; 
    propRange.lMax              = +10000; 

	// Set the range for the axis
    if (FAILED(joystick->SetProperty(DIPROP_RANGE, &propRange.diph))) {
		// if here, then setup failed. Stop.
        return DIENUM_STOP;
    }

	// If here, then success - so continue.
    return DIENUM_CONTINUE;
}

/**
 * Callback function used to find a joystick
 */
static BOOL CALLBACK enumCallback(const DIDEVICEINSTANCE* instance, VOID* context)
{
	// check input arguments
	// Get pointer to EskyInput_HandleData structure.

	assert( context != NULL && "A context (a pointer to a EskyInput_HandleData structure) MUST be specified.");
	EskyInput_HandleData *p = reinterpret_cast<EskyInput_HandleData *>(context); // Get pointer to EskyInput_HandleData structure.
	assert( p->hWnd != NULL  && "Structure must contatin a pointer to a window");
	assert( p->di != NULL    && "Structure must contatin a pointer to DirectInput");
	assert( p->joystick == NULL && "Structure should NOT contain a joystick (yet)"); 

	// Try to create the device.
	// If we fail then continue with enumeration (i.e. if this one don't work, try the next one)
	if(FAILED( p->di->CreateDevice(instance->guidInstance, &(p->joystick), NULL ))) return DIENUM_CONTINUE;
	assert( p->joystick != NULL  && "If here, then there should be a joystick instantiated" );

	//
	// Setup joystick
	//   - setup data format
	//   - setup cooperative level (non-exclucive and background)
	//   - setup axis ranges.
	//
	// If anything failes release the joystick and then continue with the enumeration
	//
	if( FAILED(p->joystick->SetDataFormat(&c_dfDIJoystick2)) ||
		FAILED(p->joystick->SetCooperativeLevel( p->hWnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND)) ||
		FAILED(p->joystick->EnumObjects(enumAxesCallback, p->joystick, DIDFT_AXIS)) 
		)
	{
		p->joystick->Unacquire();
		p->joystick->Release();
		p->joystick = NULL;
		return DIENUM_CONTINUE;
	}

	// If here, then we've successfully created and setup a joystick, stop enumerating
	return DIENUM_STOP;
}

/**
 * Finds a joystick, and creates an instance for that joystick
 *
 * The specified EskyInput_HandleData stucture should have the DirectInput and 
 * Windows handle members set (but \e not the joystick part). On successful
 * return, the joystick part in EskyInput_HandleData should be set.
 *
 * @param[in,out] p Pointer to a EskyInput_HandleData structure 
 * @return Returns true on succes, false on fail.
 */
static bool createJoystick( EskyInput_HandleData *p )
{
	// check input
	assert( p != NULL && "A EskyInput_HandleData structure must be specified" );
	assert( p->di != NULL && "The EskyInput_HandleData structure must have a pointer to a DirectInput" );
	assert( p->hWnd != NULL && "The EskyInput_HandleData structure must have a handle to a window" );
	assert( p->joystick == NULL && "The EskyInput_HandleData should NOT have a handle to a joystick" );

	/*
	 * Just enumerate all (joystick) devices. 
	 * The "find and create" code is imbedded inside the callback functions.
	 * After enumerations is done, the joystick part of the structure
	 * is set if, and only if, a joystick has been found and created.
	 */
	if(FAILED( p->di->EnumDevices( DI8DEVCLASS_GAMECTRL, enumCallback, p, DIEDFL_ATTACHEDONLY)) || p->joystick == NULL ) return false;
	assert( p->joystick != NULL && "If here, a joysick should have been created" );
	return true;
}


/**
 * Detroys an input device
 *
 * This function frees all the resources aquired by a previous call the the create() function.
 *
 * @param[in] _handle Handle to the input device.
 * @return Returns zero on success, non-zero on fail.
 */
__declspec(dllexport) 
uint32_t destroy(uint64_t _handle)
{
	// Get pointer to EskyInput_HandleData structure
	EskyInput_HandleData *p = reinterpret_cast<EskyInput_HandleData *>(_handle);

	// Olny free resources if a EskyInput_HandleData structure was given.
	if( p )
	{
		// free joystick resources
		if( p->joystick ) {
			p->joystick->Unacquire();
			p->joystick->Release(); 
		}

		// Release DirectInput
		if( p->di ) p->di->Release();

		// destroy the window
		if( p->hWnd ) DestroyWindow(p->hWnd);

		// free the memory that held the EskyInput_HandleData structure;
		delete p;
	}

	// Always return success.
    return 0;
}



/**
 * Create an input device
 *
 * @return Handle to the created device, zero on fail.
 */
__declspec(dllexport) 
uint64_t create()
{
	// Create EskyInput_HandleData stucture.
	// Return fail if this doen't succed.
	// Set structure to all zeros.
	EskyInput_HandleData *p = new EskyInput_HandleData;
	if( p == NULL ) return 0;
	memset(p,0,sizeof (EskyInput_HandleData));

	// Get handle to current module (used later).
	HINSTANCE hInstance = GetModuleHandle(NULL);

	//
	// Create a dummie window, p->hWnd. Abort if fail.
	//

	// Register dummie window class
	WNDCLASS wndclass = {0, DefWindowProc, 0,0, hInstance, NULL, NULL, NULL, NULL, _T("EskyInputWindowClass")};
	// todo: check if class already exists, and only register if it doen't?
	RegisterClass( &wndclass ); // Don't check the result since it will fail on all but first call.

	// Instantiate a window. Abort on fail.
	p->hWnd = CreateWindow( _T("EskyInputWindowClass"), _T("EskyInputWindowInstance"), 0, 
		CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
	if( p->hWnd == NULL ) goto return_fail; // Beautiful goto ;-)
	
	//
	// Create DirectInput8 (p->di), Abort if fail.
	//
	if( FAILED( DirectInput8Create( hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&(p->di), NULL)) ) {
		goto return_fail; // Beautiful goto ;-)
	}
	
	//
	// Create Joystick (p->joystick). Abort if fail.
	//
	if( createJoystick(p) == false ) goto return_fail;
	
	// if here, then success.
	// return pointer to EskyInput_HandleData instance as handle
	return reinterpret_cast<uint64_t>(p);

return_fail:
	// If here, then something went wrong.
	// Do cleanup, and retun 0;
	// note: destroy deletes p for us.
	destroy(reinterpret_cast<uint64_t>(p));
	return 0;
} 

/**
 * Retrun the number of channels of the device.
 *
 * The E-Sky USB transmitter supproted by this driver has four channels. So, this function will always return 4.
 *
 * @param[in] _handle Handle to the input device.
 * @return Returns the number of channels the device has.
 */
__declspec(dllexport) 
uint32_t get_number_of_channels(uint64_t _handle)
{
	// The E-Sky USB transmitter supproted by this driver has four channels. So, this function will always return 4.
	return 4;
}

/**
 * Capture and retrive channel data
 *
 * @param[in] _handle Handle to the input device to capture from
 * @param[out] _target Pointer to array of float to receive the data
 * @param[in] _start Index of first channel to capture
 * @param[in] _length Number of channels to receive
 * @return 0 on success, otherwise non-zero
 */
__declspec(dllexport) 
uint32_t capture(uint64_t _handle, float *_target, uint32_t _start, uint32_t _length)
{
	// Get pointer to handle data.
	EskyInput_HandleData *p = reinterpret_cast<EskyInput_HandleData *>(_handle);

	// 
	// Check input. 
	// A EskyInput_HandleData structure must be specified, and this structure must contain
	// a pointer to a DirectInput and a joystick
	//
	// If this is not the case, do nothing and retrun fail.
	//
	if( p == NULL ) return 1;
	if( p->di == NULL ) return 1;
	if( p->joystick==NULL ) return 1;

	//
	// Poll the joystick
	// Todo: aquire when create, and delete this segment?
	//
	HRESULT hr;
	hr = p->joystick->Poll();
	if( hr != DI_NOEFFECT )
	if ( FAILED(hr)) {
		hr = p->joystick->Acquire();
		while (hr == DIERR_INPUTLOST) {
			hr = p->joystick->Acquire();
		}
		if ((hr == DIERR_INVALIDPARAM) || (hr == DIERR_NOTINITIALIZED)) {
			return 2;
		}
		if (hr == DIERR_OTHERAPPHASPRIO) {
			return 3;
		}
	}
	
	// 
	// Get joystick values
	//
	DIJOYSTATE2 js;
	if (FAILED(p->joystick->GetDeviceState(sizeof(DIJOYSTATE2), &js))) { return 4; }

	//
	// Fill in the result array, then return success.
	//
	size_t index = 0;
	for( uint32_t i =_start; i<(_start+_length); ++i, ++index ) {
		if( i==0 ) {
			_target[index] = -static_cast<float>(js.lX)/10000;
		}
		else if( i==1 ) {
			_target[index] = static_cast<float>(js.lY)/10000;
		}
		else if( i==2 ) {
			_target[index] = static_cast<float>(js.lZ)/10000;
		}
		else if( i==3 ) {
			_target[index] = static_cast<float>(js.lRy)/10000;
		}
		else if( i==4 ) {
			_target[index] = static_cast<float>(js.lRx)/10000;
		}
		else _target[index] = 0.f;
	}
    return 0;
}

} // extern "C"





